﻿using SlimDX.Direct3D11;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.DXGI;
using SlimDX.DirectInput;
using Device = SlimDX.Direct3D11.Device;
using Effect = SlimDX.Direct3D11.Effect;
using Resource = SlimDX.Direct3D11.Resource;
using EffectFlags = SlimDX.D3DCompiler.EffectFlags;
using System.Drawing;
using System.Diagnostics;
using SlimDX.Multimedia;

namespace InsTron
{
    class GameView : IRenderable
    {
        private Device device;
        private DeviceContext immediateContext;
        private Effect effect;
        private EffectTechnique technique_SplitScreen;
        private EffectTechnique technique_Message;
        private InputLayout inputLayout_PosTex;
        private Buffer topHalfRectangle;
        private Buffer bottomHalfRectangle;
        private Buffer messageRectangle;
        private Texture2D pauseTexture;
        private Texture2D drawTexture;
        private Texture2D p1winsTexture;
        private Texture2D p2winsTexture;

        private GameInputHandler inputHandler;
        private PlayerViewCommons playerViewCommons;
        private PlayerView[] playerView;
        private GameState gameState;

        private bool waitingForStart;
        private bool pause;
        private bool gameOver;
        private Stopwatch stopwatch;
        private Stopwatch totalStopwatch;
        private long totalFrames;
        private float messageAlpha;


        public GameView(Device device, Keyboard keyboard)
        {
            this.device = device;
            immediateContext = device.ImmediateContext;
            gameState = new GameState();
            inputHandler = new GameInputHandler(this, keyboard);
            playerViewCommons = new PlayerViewCommons(device, gameState);
            playerView = new PlayerView[2];
            playerView[0] = new PlayerView(playerViewCommons);
            playerView[1] = new PlayerView(playerViewCommons);
            InitializeAll();

            waitingForStart = true;
            pause = false;
            gameOver = false;
            stopwatch = Stopwatch.StartNew();
            totalStopwatch = Stopwatch.StartNew();
            totalFrames = 0;
            messageAlpha = 0.0f;
        }


        public void MoveCameraTarget(float x, float y, float z) //TEMP
        {
            playerView[0].camera.Target.X += x;
            playerView[0].camera.Target.Y += y;
            playerView[0].camera.Target.Z += z;
        }


        public void RotateCamera(float dist) //TEMP
        {
            float diffX = playerView[0].camera.Target.X - playerView[0].camera.Position.X;
            float diffZ = playerView[0].camera.Target.Z - playerView[0].camera.Position.Z;
            if (System.Math.Abs(diffX) > System.Math.Abs(diffZ))
            {
                if (diffX > 0.0f)
                    playerView[0].camera.Target.Z -= dist;
                else
                    playerView[0].camera.Target.Z += dist;
            }
            else
            {
                if (diffZ > 0.0f)
                    playerView[0].camera.Target.X += dist;
                else
                    playerView[0].camera.Target.X -= dist;
            }

            if (System.Math.Abs(diffX) + System.Math.Abs(diffZ) > 4)
            {
                if (diffX > 0)
                    playerView[0].camera.Target.X -= System.Math.Abs(dist);
                else
                    playerView[0].camera.Target.X += System.Math.Abs(dist);
                if (diffZ > 0)
                    playerView[0].camera.Target.Z -= System.Math.Abs(dist);
                else
                    playerView[0].camera.Target.Z += System.Math.Abs(dist);
            }
        }


        public void InitializeAll()
        {
            LoadEffects();
            ConfigureInputLayouts();
            ConfigureInputAssember();
            PrepareSplitScreenRectangles();
            PrepareMessageRectangle();
            LoadTextures();
        }


        public void DisposeAll()
        {
            effect.Dispose();
            topHalfRectangle.Dispose();
            bottomHalfRectangle.Dispose();
            messageRectangle.Dispose();
            inputLayout_PosTex.Dispose();
            playerViewCommons.DisposeAll();
            playerView[0].DisposeAll();
            playerView[1].DisposeAll();
            drawTexture.Dispose();
            p1winsTexture.Dispose();
            p2winsTexture.Dispose();
            pauseTexture.Dispose();

            totalStopwatch.Stop();
            System.Console.WriteLine("Average FPS: {0}", ((float)totalFrames / (float)totalStopwatch.ElapsedMilliseconds) * 1000f);
        }


        private void LoadEffects()
        {
            string errors;
            using (var bytecode = ShaderBytecode.CompileFromFile("shaders\\GameViewEffects.fx", "fx_5_0",
                ShaderFlags.None, EffectFlags.None, null, new HlslIncludeHandler(), out errors))
            {
                effect = new Effect(device, bytecode);
            }
            technique_SplitScreen = effect.GetTechniqueByName("SplitScreen");
            technique_Message = effect.GetTechniqueByName("Message");
        }


        private void ConfigureInputLayouts()
        {
            ShaderSignature inputSignature;

            inputSignature = technique_SplitScreen.GetPassByIndex(0).Description.Signature;
            inputLayout_PosTex = new InputLayout(device, inputSignature, VertexDeclarations.InputElements_PosTex);
            inputSignature.Dispose();
        }


        private void ConfigureInputAssember()
        {
            immediateContext.InputAssembler.InputLayout = inputLayout_PosTex;
            immediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
        }


        private void PrepareSplitScreenRectangles()
        {
            var rectangleTop = new DataStream(20 * 6, true, true);
            rectangleTop.Write(new Vector3(-1.0f, 1.0f, 0.0f));
            rectangleTop.Write(new Vector2(0.0f, 0.0f));
            rectangleTop.Write(new Vector3(1.0f, 1.0f, 0.0f));
            rectangleTop.Write(new Vector2(1.0f, 0.0f));
            rectangleTop.Write(new Vector3(-1.0f, 0.0f, 0.0f));
            rectangleTop.Write(new Vector2(0.0f, 1.0f));
            rectangleTop.Write(new Vector3(1.0f, 1.0f, 0.0f));
            rectangleTop.Write(new Vector2(1.0f, 0.0f));
            rectangleTop.Write(new Vector3(1.0f, 0.0f, 0.0f));
            rectangleTop.Write(new Vector2(1.0f, 1.0f));
            rectangleTop.Write(new Vector3(-1.0f, 0.0f, 0.0f));
            rectangleTop.Write(new Vector2(0.0f, 1.0f));
            rectangleTop.Position = 0;

            topHalfRectangle = new Buffer(device, rectangleTop, 20 * 6, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);

            var rectangleBottom = new DataStream(20 * 6, true, true);
            rectangleBottom.Write(new Vector3(-1.0f, 0.0f, 0.0f));
            rectangleBottom.Write(new Vector2(0.0f, 0.0f));
            rectangleBottom.Write(new Vector3(1.0f, 0.0f, 0.0f));
            rectangleBottom.Write(new Vector2(1.0f, 0.0f));
            rectangleBottom.Write(new Vector3(-1.0f, -1.0f, 0.0f));
            rectangleBottom.Write(new Vector2(0.0f, 1.0f));
            rectangleBottom.Write(new Vector3(1.0f, 0.0f, 0.0f));
            rectangleBottom.Write(new Vector2(1.0f, 0.0f));
            rectangleBottom.Write(new Vector3(1.0f, -1.0f, 0.0f));
            rectangleBottom.Write(new Vector2(1.0f, 1.0f));
            rectangleBottom.Write(new Vector3(-1.0f, -1.0f, 0.0f));
            rectangleBottom.Write(new Vector2(0.0f, 1.0f));
            rectangleBottom.Position = 0;

            bottomHalfRectangle = new Buffer(device, rectangleBottom, 20 * 6, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
        }


        private void PrepareMessageRectangle()
        {
            var rectangle = new DataStream(20 * 6, true, true);
            rectangle.Write(new Vector3(-0.5f, 0.33f, 0.0f));
            rectangle.Write(new Vector2(0.0f, 0.0f));
            rectangle.Write(new Vector3(0.5f, 0.33f, 0.0f));
            rectangle.Write(new Vector2(1.0f, 0.0f));
            rectangle.Write(new Vector3(-0.5f, -0.33f, 0.0f));
            rectangle.Write(new Vector2(0.0f, 1.0f));
            rectangle.Write(new Vector3(0.5f, 0.33f, 0.0f));
            rectangle.Write(new Vector2(1.0f, 0.0f));
            rectangle.Write(new Vector3(0.5f, -0.33f, 0.0f));
            rectangle.Write(new Vector2(1.0f, 1.0f));
            rectangle.Write(new Vector3(-0.5f, -0.33f, 0.0f));
            rectangle.Write(new Vector2(0.0f, 1.0f));
            rectangle.Position = 0;

            messageRectangle = new Buffer(device, rectangle, 20 * 6, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
        }


        private void LoadTextures()
        {
            pauseTexture = Texture2D.FromFile(device, "assets\\textures\\pause.png");
            drawTexture = Texture2D.FromFile(device, "assets\\textures\\draw.png");
            p1winsTexture = Texture2D.FromFile(device, "assets\\textures\\p1wins.png");
            p2winsTexture = Texture2D.FromFile(device, "assets\\textures\\p2wins.png");
        }


        public void PauseGame()
        {
            pause = !pause;
            if (pause)
            {
                totalStopwatch.Stop();
                using (var resource = new ShaderResourceView(device, pauseTexture))
                    effect.GetVariableByName("xMessageTexture").AsResource().SetResource(resource);
            }
            else
            {
                totalStopwatch.Start();
                stopwatch.Restart();
                if (gameOver)
                {
                    SetFinalMessageTexture();
                }
            }
        }


        private void ControlTimeProgress()
        {
            playerViewCommons.RotateSkySphere(stopwatch.ElapsedMilliseconds);

            if (!pause)
            {
                if (waitingForStart)
                {
                    if (totalStopwatch.ElapsedMilliseconds >= Constants.StartDelay)
                    {
                        waitingForStart = false;
                    }
                }
                else if (gameOver)
                {
                    messageAlpha += stopwatch.ElapsedMilliseconds / 5000.0f;
                }
                else
                {
                    gameOver = gameState.Advance(stopwatch.ElapsedMilliseconds);
                    if (gameOver)
                    {
                        SetFinalMessageTexture();
                    }
                }

                UpdateCameras(stopwatch.ElapsedMilliseconds);
            }

            stopwatch.Restart();
        }


        private void SetFinalMessageTexture()
        {
            if (gameState.Crashed[0] == false)
            {
                using (var resource = new ShaderResourceView(device, p1winsTexture))
                    effect.GetVariableByName("xMessageTexture").AsResource().SetResource(resource);
            }
            else if (gameState.Crashed[1] == false)
            {
                using (var resource = new ShaderResourceView(device, p2winsTexture))
                    effect.GetVariableByName("xMessageTexture").AsResource().SetResource(resource);
            }
            else
            {
                using (var resource = new ShaderResourceView(device, drawTexture))
                    effect.GetVariableByName("xMessageTexture").AsResource().SetResource(resource);
            }
        }


        public void EnableBoost(int idx, bool enabled)
        {
            gameState.PlayerState[idx].Boost = enabled;
        }


        public void EnableBrake(int idx, bool enabled)
        {
            gameState.PlayerState[idx].Brake = enabled;
        }


        public void ProcessTurn(int idx, bool left)
        {
            if (waitingForStart)
                return;
            //gameState.Advance(stopwatch.ElapsedMilliseconds);
            gameState.InitiateTurnPlayer(idx, left);
            //stopwatch.Restart();
        }


        private void UpdateCameras(float time)
        {
            if (gameOver)
            {
                playerView[0].UpdateCameraFinal(0, time);
                playerView[1].UpdateCameraFinal(1, time);
            }
            else
            {
                playerView[0].UpdateCamera(gameState.PlayerState[0].Position, gameState.PlayerState[0].FactualDirection, time);
                playerView[1].UpdateCamera(gameState.PlayerState[1].Position, gameState.PlayerState[1].FactualDirection, time);
            }
        }


        public void RenderScene()
        {
            inputHandler.ReadKeyboard();
            ControlTimeProgress();

            playerView[0].RenderScene(immediateContext);
            playerView[1].RenderScene(immediateContext);

            // drawing final image - top rectangle
            immediateContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(topHalfRectangle, 20, 0));
            using (var resource = new ShaderResourceView(device, playerView[0].TargetTexture))
                effect.GetVariableByName("xFinalImageHalf").AsResource().SetResource(resource);
            for (int i = 0; i < technique_SplitScreen.Description.PassCount; i++)
            {
                technique_SplitScreen.GetPassByIndex(i).Apply(immediateContext);
                immediateContext.Draw(6, 0);
            }

            // drawing final image - bottom rectangle
            immediateContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(bottomHalfRectangle, 20, 0));
            using (var resource = new ShaderResourceView(device, playerView[1].TargetTexture))
                effect.GetVariableByName("xFinalImageHalf").AsResource().SetResource(resource);
            for (int i = 0; i < technique_SplitScreen.Description.PassCount; i++)
            {
                technique_SplitScreen.GetPassByIndex(i).Apply(immediateContext);
                immediateContext.Draw(6, 0);
            }

            if (gameOver || pause)
            {
                effect.GetVariableByName("xMessageAlpha").AsScalar().Set(pause? 1.0f : messageAlpha);
                immediateContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(messageRectangle, 20, 0));
                for (int i = 0; i < technique_Message.Description.PassCount; i++)
                {
                    technique_Message.GetPassByIndex(i).Apply(immediateContext);
                    immediateContext.Draw(6, 0);
                }
            }

            totalFrames++;
        }
    }
}
